// Copyright (C) 2009 Jesse Jones
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using Mono.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Mono.Options
{
	/// <summary>This namespace contains the command-line options processing library distributed by the mono team.</summary>
	internal sealed class NamespaceDoc
	{
	}
}

namespace MmmDoc
{
	/// <summary>Various user settings.</summary>
	/// <remarks>At the moment these are all set via the command line (except Language
	/// which may also be set via an environment variable).</remarks>
	internal static class Settings	// has to be static or the OptionSet field initializer doesn't work
	{
		public static void Parse(string[] args)
		{
			Contract.Requires(args != null, "args is null");
			
			ExpandTabs = 3;
			
			List<string> files = ms_options.Parse(args);
			DoValidateArgs(files);
			
			if (Language == null)
			{
				Language = System.Environment.GetEnvironmentVariable("LANGUAGE");
				if (Language == null)
					Language = "en_US";
			}
			
			foreach (string pair in files)
			{
				int i = pair.IndexOf(',');
				
				ms_assemblies.Add(pair.Substring(0, i));
				ms_helpFiles.Add(pair.Substring(i + 1));
			}
		}
		
		/// <summary>The assemblies associated with the xml help files.</summary>
		public static IEnumerable<string> Assemblies {get {return ms_assemblies;}}
		
		/// <summary>Directory containing custom css files.</summary>
		/// <remarks>May be null.</remarks>
		public static string CssDir {get; private set;}
		
		/// <summary>Where the user told us to look for hunspell dictionaries.</summary>
		/// <remarks>May be null.</remarks>
		public static string DictsDir {get; private set;}
		
		/// <summary>Number of spaces to use when replacing tabs within code elements.</summary>
		/// <remarks>Defaults to three. If zero tabs are not replaced.</remarks>
		public static int ExpandTabs {get; private set;}
		
		/// <summary>Path to an extra dictionary file.</summary>
		/// <remarks>May be null.</remarks>
		public static string ExtraDictFile {get; private set;}
		
		/// <summary>Path to an xml file (normally) generated by compilers.</summary>
		public static IEnumerable<string> HelpFiles {get {return ms_helpFiles;}}
		
		/// <summary>Language to be used for the HTML tag and for spell checking.</summary>
		/// <remarks>Often will include a locale, e.g. en_US.</remarks>
		public static string Language {get; private set;}
		
		/// <summary>Directory to place the generated files into.</summary>
		public static string OutDir {get; private set;}
		
		/// <summary>Links to external documentation.</summary>
		public static IEnumerable<string> SeeAlsoLinks		{get {return ms_seeAlso;}}
		
		/// <summary>If positive write details to stdout.</summary>
		public static int Verbosity											 {get {return ms_verbosity;}}
		
		/// <summary>Visibility a type or member must have for us to generate help for it.</summary>
		public static Visibility Visibility {get {return ms_visiblity;}}
		
		/// <summary>If true then write to stderr if a type or method is missing documentation.</summary>
		public static bool WarnIfMissing {get; private set;}
		
		#region Private Methods
		private static void DoShowVersion(string value)
		{
			Version version = Assembly.GetExecutingAssembly().GetName().Version;
			Console.WriteLine("mmmdoc {0}", version);
			
			Environment.Exit(0);
		}
		
		private static void DoSetVisiblity(string value)
		{
			switch (value)
			{
				case "external":
					ms_visiblity = Visibility.External;
					break;
					
				case "internal":
					ms_visiblity = Visibility.Internal;
					break;
					
				case "all":
					ms_visiblity = Visibility.All;
					break;
					
				default:
					Console.Error.WriteLine("Visibility switch must be set to 'external', 'internal', or 'all' not '{0}'.", value);
					Environment.Exit(1);
					break;
			}
		}
		
		private static void DoValidateArgs(List<string> files)
		{
			var bad = from f in files where f.StartsWith("-") select f;
			if (bad.Any())
			{
				Console.Error.WriteLine("Unknown option: {0}", string.Join(" ", bad.ToArray()));
				Environment.Exit(1);
			}
			
			if (files.Count == 0)
			{
				Console.Error.WriteLine("At least one assembly,xml-file pair must be set.");
				Environment.Exit(1);
			}
			
			foreach (string pair in files)
			{
				int i = pair.IndexOf(',');
				if (i < 0)
				{
					Console.Error.WriteLine("Expected 'assembly,xml-file', but found '{0}'", pair);
					Environment.Exit(1);
				}
				
				string assembly = pair.Substring(0, i);
				string xml = pair.Substring(i + 1);
				DoValidateFile(assembly);
				DoValidateFile(xml);
			}
			
			if (OutDir == null)
			{
				Console.Error.WriteLine("The output directory must be set.");
				Environment.Exit(1);
			}
			
			if (!System.IO.Directory.Exists(OutDir))
				System.IO.Directory.CreateDirectory(OutDir);
			
			if (CssDir != null)
				DoValidateDir("--css", CssDir);
			if (DictsDir != null)
				DoValidateDir("--dicts", DictsDir);
			if (ExtraDictFile != null)
				DoValidateFile(ExtraDictFile);
			
			foreach (string link in ms_seeAlso)
			{
				if (!link.Contains(" "))
				{
					Console.Error.WriteLine("see-also links must be followed by a space and then a name.");
					Environment.Exit(1);
				}
			}
		}
		
		private static void DoValidateFile(string path)
		{
			if (!System.IO.File.Exists(path))
			{
				Console.Error.WriteLine("The file '{0}' does not exist.", path);
				Environment.Exit(1);
			}
		}
		
		private static void DoValidateDir(string option, string path)
		{
			if (!System.IO.Directory.Exists(path))
			{
				Console.Error.WriteLine("The {0} directory '{1}' does not exist.", option, path);
				Environment.Exit(1);
			}
		}
		
		private static void DoShowHelp(string value)
		{
			Console.WriteLine("mono mmmdoc.exe [options] --out=dir assembly,xml-file");
			ms_options.WriteOptionDescriptions(Console.Out);
			Console.WriteLine("Note that multiple assembly,xml-file pairs may be specified.");
			
			Environment.Exit(0);
		}
		#endregion
		
		#region Fields
		private static List<string> ms_assemblies = new List<string>();
		private static List<string> ms_helpFiles = new List<string>();
		private static List<string> ms_seeAlso = new List<string>();
		private static int ms_verbosity;
		private static Visibility ms_visiblity;
		
		// TODO: might an an --error-if-missing switch too. Or maybe --treat-warnings-as-errors.
		private static OptionSet ms_options = new OptionSet()
		{
			{"css=", "directory containing css files to be appended to the default css files", v => {CssDir = v;}},
			{"dicts=", "path to use for default hunspell dictionaries", v => {DictsDir = v;}},
			{"expand-tabs=", "number of spaces to use when replacing tabs within code elements", v => {ExpandTabs = int.Parse(v);}},
			{"extra-dict=", "path to a custom *.dict file", v => {ExtraDictFile = v;}},
			{"h|?|help", "prints this message and exits", Settings.DoShowHelp},
			{"language=", "used to load a hunspell dictionary and set the HTML language", v => {Language = v;}},
			{"o=|out=", "directory the documentation will be placed within", v => {OutDir = v;}},
			{"see-also=", "adds links to the namespaces table of contents", v => ms_seeAlso.Add(v)},
			{"verbose", "writes progress info to stdout, may be used more than once", v => ++ms_verbosity},
			{"version", "prints the version number and exits", Settings.DoShowVersion},
			{"visibility=", "external (the default), internal, or all", Settings.DoSetVisiblity},
			{"warn-if-missing", "write to stderr if documentation is missing for a type or method", v => {WarnIfMissing = true;}},
		};
		#endregion
	}
}
